import utils from '../utils'
import Geom from '../geom'
import Dataset from '../dataset'
import { ArcCollection } from '../paths'
import { GeoJSON, importCRS } from '../geojson'
import Common from '../common.js'
import Polygons from '../polygons'

// Convert a TopoJSON topology into mapshaper's internal format
// Side-effect: data in topology is modified
function importTopoJSON(topology, opts) {
  let dataset = null
  let arcs = null
  let layers = null
  if (utils.isString(topology)) {
    topology = JSON.parse(topology)
  }
  if (topology.arcs && topology.arcs.length > 0) {
    if (topology.transform) {
      decodeArcs(topology.arcs, topology.transform)
    }
    if (opts && opts.precision) {
      roundCoords(topology.arcs, opts.precision)
    }
    arcs = new ArcCollection(topology.arcs)
  }
  layers = Object.keys(topology.objects).reduce((memo, name) => {
    const layers = importObject(topology.objects[name], arcs, opts)
    let lyr = null
    for (let i = 0, n = layers.length; i < n; i += 1) {
      lyr = layers[i]
      lyr.name = name
      memo.push(lyr)
    }
    return memo
  }, [])
  layers.forEach((lyr) => {
    if (Common.layerHasPaths(lyr)) {
      Polygons.cleanShapes(lyr.shapes, arcs, lyr.geometry_type)
    }
    if (lyr.geometry_type == 'point' && topology.transform) {
      decodePoints(lyr.shapes, topology.transform)
    }
  })
  dataset = {
    layers: layers,
    arcs: arcs,
    info: {}
  }
  importCRS(dataset, topology)
  if (topology.metadata) {
    Dataset.importMetadata(dataset, topology.metadata)
  }
  return dataset
}
function decodePoints(shapes, transform) {
  utils.forEachPoint(shapes, (p) => {
    p[0] = p[0] * transform.scale[0] + transform.translate[0]
    p[1] = p[1] * transform.scale[1] + transform.translate[1]
  })
}
function decodeArcs(arcs, transform) {
  const mx = transform.scale[0]
  const my = transform.scale[1]
  const bx = transform.translate[0]
  const by = transform.translate[1]

  arcs.forEach((arc) => {
    let prevX = 0
    let prevY = 0
    let xy =null
    let x = null
    let y = null
    for (let i=0, len=arc.length; i<len; i++) {
      xy = arc[i]
      x = xy[0] + prevX
      y = xy[1] + prevY
      xy[0] = x * mx + bx
      xy[1] = y * my + by
      prevX = x
      prevY = y
    }
  })
}

function roundCoords(arcs, precision) {
  const round = Geom.getRoundingFunction(precision)
  let p = null
  arcs.forEach((arc) => {
    for (let i=0, len=arc.length; i<len; i++) {
      p = arc[i]
      p[0] = round(p[0])
      p[1] = round(p[1])
    }
  })
}
function importObject(obj, arcs, opts) {
  const importer = new GeometryImporter(arcs, opts)
  const geometries = obj.type == 'GeometryCollection' ? obj.geometries : [obj]
  geometries.forEach(importer.addGeometryObject, importer)
  return importer.done()
}
function GeometryImporter(arcs, opts) {
  const idField = opts && opts.id_field || GeoJSON.ID_FIELD
  const properties = []
  const shapes = [] // topological ids
  const types = []
  let dataNulls = 0
  let shapeNulls = 0
  let collectionType = null
  let shapeId = null

  this.addGeometryObject = function(geom) {
    let rec = geom.properties || null
    shapeId = shapes.length
    shapes[shapeId] = null
    if ('id' in geom) {
      rec = rec || {}
      rec[idField] = geom.id
    }
    properties[shapeId] = rec
    if (!rec) dataNulls++
    if (geom.type) {
      this.addShape(geom)
    }
    if (shapes[shapeId] === null) {
      shapeNulls++
    }
  }
  this.addShape = function(geom) {
    const curr = shapes[shapeId];
    const type = GeoJSON.translateGeoJSONType(geom.type)
    let shape = null
    if (geom.type === 'GeometryCollection') {
      geom.geometries.forEach(this.addShape, this)
    } else if (type) {
      this.setGeometryType(type)
      shape = shapeImporters[geom.type](geom, arcs)
      // TODO: better shape validation
      if (!shape || !shape.length) {
        // do nothing
      } else if (!Array.isArray(shape[0])) {
        // stop("Invalid TopoJSON", geom.type, "geometry")
        utils.error(`Invalid TopoJSON ${geom.type}`)
      } else {
        shapes[shapeId] = curr ? curr.concat(shape) : shape
      }
    } else if (geom.type) {
      // stop("Invalid TopoJSON geometry type:", geom.type)
      utils.error(`Invalid TopoJSON geometry type:${geom.type}`)
    }
  }
  this.setGeometryType = function(type) {
    const currType = shapeId < types.length ? types[shapeId] : null
    if (!currType) {
      types[shapeId] = type
      this.updateCollectionType(type)
    } else if (currType != type) {
      // stop("Unable to import mixed-type TopoJSON geometries")
      utils.error(`Unable to import mixed-type TopoJSON geometries`)
    }
  }
  this.updateCollectionType = function(type) {
    if (!collectionType) {
      collectionType = type
    } else if (type && collectionType != type) {
      collectionType = 'mixed'
    }
  }
}
function importPolygonArcs(rings, arcs) {
  let ring = rings[0]
  let imported = null
  let area = null
  if (!arcs) {
    // stop("Invalid TopoJSON file: missing arc data.")
    utils.error(`Invalid TopoJSON file: missing arc data.`)
  }
  area = ring ? Geom.getPlanarPathArea(ring, arcs) : null
  if (!area) {
    return null
  }
  if (area < 0) utils.reversePath(ring)
  imported = [ring]
  for (let i=1; i<rings.length; i++) {
    ring = rings[i]
    area = Geom.getPlanarPathArea(ring, arcs)
    if (!area) continue
    if (area > 0) utils.reversePath(ring)
    imported.push(ring)
  }
  return imported
}
const shapeImporters = {
  Point: function(geom) {
    return [geom.coordinates]
  },
  MultiPoint: function(geom) {
    return geom.coordinates
  },
  LineString: function(geom) {
    return [geom.arcs]
  },
  MultiLineString: function(geom) {
    return geom.arcs
  },
  Polygon: function(geom, arcColl) {
    return importPolygonArcs(geom.arcs, arcColl)
  },
  MultiPolygon: function(geom, arcColl) {
    return geom.arcs.reduce((memo, arr) => {
      let rings = importPolygonArcs(arr, arcColl)
      if (rings) {
        memo = memo ? memo.concat(rings) : rings
      }
      return memo
    }, null)
  }
}

export default {
  importTopoJSON,
}